#!/usr/bin/env ruby

# Copyright (c) 2021-2022 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

include_relative 'common.irt'

function(:EmptyPostWriteBarrier,
        params: {},
        regmap: $full_regmap,
        regalloc_set: RegMask.new,
        mode: [:FastPath]) {
    if Options.arch == :arm32
        Intrinsic(:UNREACHABLE).Terminator.void
        next
    end
    ReturnVoid().void
}

def PostInterGenerationalBarrier(obj_num)
    if obj_num == 0
        params = {mem: 'ref_uint'}
        mask = RegMask.new($full_regmap, :arg0, :tmp0, :tmp1)
    elsif obj_num == 1
        params = {mem: 'ref_uint', offset: 'word', obj1: 'ref_uint'}
        mask = RegMask.new($full_regmap, :arg0, :arg1, :tmp0)
    elsif obj_num == 2
        params = {mem: 'ref_uint', offset: 'word', obj1: 'ref_uint', obj2: 'ref_uint'}
        mask = RegMask.new($full_regmap, :arg0, :arg1, :arg2)
    else
        raise "Wrong obj_num #{obj_num}"
    end

    function("PostInterGenerationalBarrier#{obj_num}".to_sym,
             params: params,
             regmap: $full_regmap,
             regalloc_set: mask,
             mode: [:FastPath]) {
        if Options.arch == :arm32
            Intrinsic(:UNREACHABLE).Terminator.void
            next
        end

        min_addr := LoadI(%tr).Imm(Constants::TLS_CARD_TABLE_MIN_ADDR_OFFSET).word
        cards := LoadI(%tr).Imm(Constants::TLS_CARD_TABLE_ADDR_OFFSET).ptr

        mem_word := Cast(mem).SrcType(Constants::REF_UINT).word
        card_offset := ShrI(Sub(mem_word, min_addr).word).Imm(Constants::CARD_TABLE_CARD_BITS).word
        card := Add(cards, card_offset).ptr
        StoreI(card, Constants::CARD_DIRTY_VALUE).Imm(Constants::CARD_VALUE_OFFSET).u8
        ReturnVoid().void
   }
end

# G1 PostWrite barrier
# - Checks if mem and obj are in different regions
# - Checks if GC Card for mem is not marked, then marks it
# - Pushes Card to G1 LockFreeBuffer concurrently, GC consumer thread will fetch the Card
def PostInterRegionBarrierFast(obj_num, check_null)
    no_check = check_null ? "" : "NoCheck"

    if obj_num == 1
        params = {mem: 'ref_uint', offset: 'word', obj1: 'ref_uint'}
        mask = RegMask.new($full_regmap, :arg0, :arg1, :tmp0)
    elsif obj_num == 2
        params = {mem: 'ref_uint', offset: 'word', obj1: 'ref_uint', obj2: 'ref_uint'}
        mask = RegMask.new($full_regmap, :arg0, :arg1, :arg2)
        if Options.arch == :x86_64
            mask += :tmp0
        end
    else
        raise "Wrong obj_num #{obj_num}"
    end

    function("PostInterRegionBarrierFast#{no_check}#{obj_num}".to_sym,
             params: params,
             regmap: $full_regmap,
             regalloc_set: mask,
             mode: [:FastPath]) {
        if Options.arch == :arm32
            Intrinsic(:UNREACHABLE).Terminator.void
            next
        end

        if obj_num == 1
            if check_null
                If(obj1, 0).EQ.Unlikely {
                    Goto(:Done)
                }
            end

            If(ShrI(Xor(mem, obj1).ref_uint).Imm(Constants::REGION_SIZE_BIT).ref_uint, 0).EQ.Unlikely {
                Goto(:Done)
            }
        elsif obj_num == 2
            if check_null
                If(obj1, 0).EQ.Unlikely {
                    Goto(:Check2)
                }
            end

            If(ShrI(Xor(mem, obj1).ref_uint).Imm(Constants::REGION_SIZE_BIT).ref_uint, 0).NE {
                Goto(:PushToBuffer)
            }
    
        Label(:Check2)
            if check_null
                If(obj2, 0).EQ.Unlikely {
                    Goto(:Done)
                }
            end
    
            If(ShrI(Xor(mem, obj2).ref_uint).Imm(Constants::REGION_SIZE_BIT).ref_uint, 0).EQ.Unlikely {
                Goto(:Done)
            }
        end

    Label(:PushToBuffer)
        ref_addr := Add(mem, offset).ref_uint
        LiveOut(ref_addr).DstReg(regmap[:arg0]).ref_uint
        if obj_num == 1
            Intrinsic(:TAIL_CALL).AddImm(Constants::POST_INTER_REGION_BARRIER_SLOW).Terminator.void
        elsif obj_num == 2
            IfImm(AndI(ref_addr).Imm(Constants::REFERENCE_PAIR_ALIGNMENT_MASK).ref_uint).Imm(0).EQ {
                Intrinsic(:TAIL_CALL).AddImm(Constants::POST_INTER_REGION_BARRIER_SLOW).Terminator.void
            }
            Intrinsic(:TAIL_CALL).AddImm(Constants::POST_INTER_REGION_BARRIER_PAIR_UNALIGNED_SLOW).Terminator.void
        end

    Label(:Done)
        ReturnVoid().void
    }
end

scoped_macro(:push_dirty_card_to_buffer) do |cards, min_addr, mem|
    mem_word := Cast(mem).SrcType(Constants::REF_UINT).word
    card_offset := ShrI(Sub(mem_word, min_addr).word).Imm(Constants::CARD_TABLE_CARD_BITS).word
    card := Add(cards, card_offset).ptr
    card_value := LoadI(card).Imm(Constants::CARD_VALUE_OFFSET).u8

    is_card_clear := Compare(card_value, Constants::CARD_CLEAR_VALUE).EQ.b
    IfImm(is_card_clear).Imm(0).EQ.Unlikely.b {
        Goto(:Done)
    }
    StoreI(card, Constants::CARD_MARKED_VALUE).Imm(Constants::CARD_VALUE_OFFSET).u8

    buffer := LoadI(%tr).Imm(Constants::MANAGED_THREAD_G1_POST_BARRIER_BUFFER_OFFSET).ptr
    buffer_data := AddI(buffer).Imm(Constants::G1_LOCK_BUFFER_DATA_OFFSET).ptr
    tail_index := LoadI(buffer).Imm(Constants::G1_LOCK_BUFFER_TAIL_OFFSET).word
    tail_offset := ShlI(tail_index).Imm(Constants::POINTER_LOG_SIZE).word
    next_tail_index := AndI(AddI(tail_index).Imm(1).word).Imm(Constants::G1_LOCK_BUFFER_SIZE_MASK).word

    Label(:TryPushLoop)
    head_index := LoadI(buffer).Imm(Constants::G1_LOCK_BUFFER_HEAD_OFFSET).Volatile.word
    If(next_tail_index, head_index).EQ {
        Goto(:TryPushLoop)
    }

    Store(buffer_data, tail_offset, card).ptr
    StoreI(buffer, next_tail_index).Imm(Constants::G1_LOCK_BUFFER_TAIL_OFFSET).Volatile.word

    Label(:Done)
end

def PostInterRegionBarrierSlow()
    if Options.arch == :arm64
        mask = RegMask.new($full_regmap, :arg0, :callee0, :callee1, :callee2, :tmp0, :tmp1)
    elsif Options.arch == :x86_64
        mask = RegMask.new($full_regmap, :arg0, :callee0, :caller0, :caller3, :tmp0, :tmp1)
    else
        mask = $panda_mask
    end

    function(:PostInterRegionBarrierSlow,
            params: {mem: 'ref_uint'},
            regmap: $full_regmap,
            regalloc_set: mask,
            mode: [:FastPath]) {
        if Options.arch == :arm32
            Intrinsic(:UNREACHABLE).Terminator.void
            next
        end

        min_addr := LoadI(%tr).Imm(Constants::TLS_CARD_TABLE_MIN_ADDR_OFFSET).word
        cards := LoadI(%tr).Imm(Constants::TLS_CARD_TABLE_ADDR_OFFSET).ptr
        push_dirty_card_to_buffer(cards, min_addr, mem)
        ReturnVoid().void
    }
end

def PostInterRegionBarrierPairUnalignedSlow()
    if Options.arch == :arm64
        mask = RegMask.new($full_regmap, :arg0, :callee0, :callee1, :callee2, :tmp0, :tmp1)
    elsif Options.arch == :x86_64
        mask = RegMask.new($full_regmap, :arg0, :callee0, :caller0, :caller3, :tmp0, :tmp1)
    else
        mask = $panda_mask
    end

    function(:PostInterRegionBarrierPairUnalignedSlow,
            params: {mem: 'ref_uint'},
            regmap: $full_regmap,
            regalloc_set: mask,
            mode: [:FastPath]) {
        if Options.arch == :arm32
            Intrinsic(:UNREACHABLE).Terminator.void
            next
        end

        min_addr := LoadI(%tr).Imm(Constants::TLS_CARD_TABLE_MIN_ADDR_OFFSET).word
        cards := LoadI(%tr).Imm(Constants::TLS_CARD_TABLE_ADDR_OFFSET).ptr
        push_dirty_card_to_buffer(cards, min_addr, mem)
        push_dirty_card_to_buffer(cards, min_addr, AddI(mem).Imm(Constants::REFERENCE_TYPE_SIZE).ref_uint)
        ReturnVoid().void
    }
end

PostInterGenerationalBarrier(0)
PostInterGenerationalBarrier(1)
PostInterGenerationalBarrier(2)

PostInterRegionBarrierFast(1, false)
PostInterRegionBarrierFast(1, true)
PostInterRegionBarrierFast(2, true)

PostInterRegionBarrierSlow()
PostInterRegionBarrierPairUnalignedSlow()
